package com.openlogic.common.db.api.plugin;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.openlogic.common.db.api.utils.DataSourceUtils;
import com.openlogic.common.db.api.utils.PropertyUtils;
import com.openlogic.common.spi.constants.CommonConstants;
import com.openlogic.common.spi.datasource.DataSourceChannel;
import com.openlogic.common.spi.datasource.DataSourceClient;
import com.openlogic.common.spi.enums.DbType;
import com.openlogic.datasource.api.param.BaseConnectionParam;
import com.openlogic.datasource.api.param.ConnectionParam;

/**
 * @ClassName: DataSourceClientProvider
 * @Description: 数据源客户端提供者
 * @author weiyongpeng
 * @date 2024年11月16日 上午8:31:33
 */
public class DataSourceClientProvider {

	private static final Logger log = LoggerFactory.getLogger(DataSourceClientProvider.class);

	/**
	 * 使用缓存避免每次创建新的数据源，一个DataSourceClient对应唯一一个数据源
	 */
	private static final long duration = PropertyUtils.getLong(CommonConstants.KERBEROS_EXPIRE_TIME, 24L);
    private static final Cache<String, DataSourceClient> uniqueId2dataSourceClientCache = CacheBuilder.newBuilder()
        .expireAfterWrite(duration, TimeUnit.HOURS)
        .removalListener((RemovalListener<String, DataSourceClient>) notification -> {
            try (DataSourceClient closedClient = notification.getValue()) {
                log.info("Datasource: {} is removed from cache due to expire", notification.getKey());
            }
        })
        .maximumSize(100)
        .build();
    private DataSourcePluginManager dataSourcePluginManager;

    private DataSourceClientProvider() {
        initDataSourcePlugin();
    }

    private static class DataSourceClientProviderHolder {
        private static final DataSourceClientProvider INSTANCE = new DataSourceClientProvider();
    }

    public static DataSourceClientProvider getInstance() {
        return DataSourceClientProviderHolder.INSTANCE;
    }

    public Connection getConnection(DbType dbType, ConnectionParam connectionParam) throws ExecutionException, SQLException {
        BaseConnectionParam baseConnectionParam = (BaseConnectionParam) connectionParam;
        String datasourceUniqueId = DataSourceUtils.getDatasourceUniqueId(baseConnectionParam, dbType);
        log.info("Get connection from datasource {}", datasourceUniqueId);

        DataSourceClient dataSourceClient = uniqueId2dataSourceClientCache.get(datasourceUniqueId, () -> {
            Map<String, DataSourceChannel> dataSourceChannelMap = dataSourcePluginManager.getDataSourceChannelMap();
            DataSourceChannel dataSourceChannel = dataSourceChannelMap.get(dbType.getDesc());
            if (null == dataSourceChannel) {
                throw new RuntimeException(String.format("datasource plugin '%s' is not found", dbType.getDesc()));
            }
            return dataSourceChannel.createDataSourceClient(baseConnectionParam, dbType);
        });
        return dataSourceClient.getConnection();
    }
    
    public DataSourceClient getDataSourceClient(DbType dbType, ConnectionParam connectionParam) throws ExecutionException, SQLException {
        BaseConnectionParam baseConnectionParam = (BaseConnectionParam) connectionParam;
        String datasourceUniqueId = DataSourceUtils.getDatasourceUniqueId(baseConnectionParam, dbType);
        log.info("Get connection from datasource {}", datasourceUniqueId);

        DataSourceClient dataSourceClient = uniqueId2dataSourceClientCache.get(datasourceUniqueId, () -> {
            Map<String, DataSourceChannel> dataSourceChannelMap = dataSourcePluginManager.getDataSourceChannelMap();
            DataSourceChannel dataSourceChannel = dataSourceChannelMap.get(dbType.getDesc());
            if (null == dataSourceChannel) {
                throw new RuntimeException(String.format("datasource plugin '%s' is not found", dbType.getDesc()));
            }
            return dataSourceChannel.createDataSourceClient(baseConnectionParam, dbType);
        });
        return dataSourceClient;
    }
    
    private void initDataSourcePlugin() {
        dataSourcePluginManager = new DataSourcePluginManager();
        dataSourcePluginManager.installPlugin();
    }

}
